/*
 *
 * @(#) $Id: html_editor.js,v 1.41 2009/10/11 22:24:59 mlemos Exp $
 *
 */

if(ML === undefined)
	var ML = { }

if(ML.HTMLEditor === undefined)
	ML.HTMLEditor = { }

if(ML.HTMLEditor.Editor === undefined)
{

ML.HTMLEditor.HTMLEditors = {}

ML.HTMLEditor.defaultVisualToolbar = {
	'character': [
		{
			type: 'bold'
		},
		{
			type: 'italic'
		},
		{
			type: 'underline'
		},
		{
			type: 'strikethrough'
		},
		{
			type: 'createlink'
		},
		{
			type: 'unlink'
		}
	],
	'paragraph': [
		{
			type: 'justifyleft'
		},
		{
			type: 'justifycenter'
		},
		{
			type: 'justifyright'
		},
		{
			type: 'justifyfull'
		},
		{
			type: 'space'
		},
		{
			type: 'formatblock'
		},
		{
			type: 'inserttemplate'
		}
	],
	'document': [
		{
			type: 'copy'
		},
		{
			type: 'cut'
		},
		{
			type: 'paste'
		},
		{
			type: 'delete'
		},
		{
			type: 'space'
		},
		{
			type: 'undo'
		},
		{
			type: 'redo'
		},
		{
			type: 'space'
		},
		{
			type: 'html'
		}
	]
}

ML.HTMLEditor.defaultHTMLToolbar = {
	'document': [
		{
			type: 'visual'
		}
	]
}

ML.HTMLEditor.linkEmulationStyle = 'color: #0000FF; text-decoration: underline'

ML.HTMLEditor.Editor = function()
{
	this.id = ''
	this.error = ''
	this.debug = true
	this.editorStyle = 'background-color: #ffffff; border-style: solid; border-width: 1px; margin: 0px; border-color:  #707070 #e0e0e0 #e0e0e0 #707070'
	this.templateVariableStyle = 'border-style: dashed; border-width: 1px; margin: 1px; padding: 1px'
	this.templateMarkStyle = 'background-color: #cedee6; border-style: solid; border-width: 1px; margin: 0px; padding: 2px; border-color: #e0e0e0 #707070 #707070 #e0e0e0; font-size: 8pt; opacity: 0.75; filter:alpha(opacity=75)'
	this.menuStyle = 'background-color: #d0d0d0; border-style: solid; border-width: 1px; margin: 0px; border-color: #e0e0e0 #707070 #707070 #e0e0e0'
	this.itemStyle = 'padding: 4px; color: #000000'
	this.itemSelectStyle = 'padding: 4px; color: #ffffff; background-color: #000080'
	this.mode = 'visual'
	this.templateVariables = {}
	this.externalCSS = []
	this.openVariable = '{'
	this.closeVariable = '}'
	this.alternativesMark = '+'

	/*
	 *  private variables
	 */
	this.editor = null
	this.textarea = null
	this.iframe = null
	this.editorDocument = null
	this.htmlEditor = null
	this.visualEditor = null
	this.lastMenu = ''
	this.lastMenuTime = 0
	this.menuDelayTime = 100
	this.lastOpenedMenu = null

	var hasAttribute = function (element, attribute)
	{
		if(element.hasAttribute)
			return(element.hasAttribute(attribute))
		return typeof(element.attributes[attribute]) != 'undefined'
	}

	var getElementsByName = function(element, name, tags)
	{
		var l = []
		for(var t = 0; t < tags.length; ++t)
		{
			var e = element.getElementsByTagName(tags[t])
			for(var i = 0; i < e.length; ++i)
				if(e[i].getAttribute('name') == name)
					l[l.length] = e[i]
		}
		return l
	}

	var getElementBox = function (element)
	{
		var box = {}
		var d = element.ownerDocument
		var win = d.defaultView
		if(element.getBoundingClientRect)
		{
			var b = element.getBoundingClientRect()
			box.x = b.left + d.body.scrollLeft
			box.y = b.top + d.body.scrollTop
			box.width = b.right - b.left + 1
			box.height = b.bottom - b.top + 1
		}
		else
		{
			if(d.getBoxObjectFor)
			{
				var b = d.getBoxObjectFor(element)
				box.x = b.x
				box.y = b.y
				box.width = b.width
				box.height = b.height
				if(win.getComputedStyle)
				{
					var s = win.getComputedStyle(element, null)
					var o = parseInt(s.borderLeftWidth) + parseInt(s.borderRightWidth)
					box.x -= o
					box.width += o
					o = parseInt(s.borderTopWidth) + parseInt(s.borderBottomWidth)
					box.y -= o
					box.height += o
				}
			}
			else
			{
				var p = element.style.position
				element.style.position = 'relative'
				box.x = element.offsetLeft
				box.y = element.offsetTop
				box.width = element.offsetWidth
				box.height = element.offsetHeight
				element.style.position = p
			}
		}
		return box
	}

	var getElementSize = function (element)
	{
		var box = getElementBox(element)
		return { width: box.width, height: box.height }
	}

	var repositionElement = function (element, parent, frame)
	{
		var d = parent.ownerDocument
		var win = d.defaultView
		var b = getElementBox(parent)
		var x = b.x
		var y = b.y + b.height
		var w = parseInt(b.width)
		if(!isNaN(w))
		{
			var s = getElementSize(element)
			if(!isNaN(parseInt(s.width)))
			{
				x += (w - parseInt(s.width)) / 2
				if(x < 0)
					x = 0
			}
		}
		if(frame)
		{
			b = getElementBox(frame)
			x += b.x
			y += b.y
			if(typeof(frame.contentWindow.pageXOffset) == 'number')
			{
				x -= frame.contentWindow.pageXOffset
				y -= frame.contentWindow.pageYOffset
			}
			else
				if(frame.contentWindow.document.documentElement
				&& frame.contentWindow.document.compatMode
				&& frame.contentWindow.document.compatMode != "BackCompat")
				{
					x -= frame.contentWindow.document.documentElement.scrollLeft
					y -= frame.contentWindow.document.documentElement.scrollTop
				}
				else
				{
					x -= frame.contentWindow.document.body.scrollLeft
					y -= frame.contentWindow.document.body.scrollTop
				}
		}
		element.style.left = x + 'px'
		element.style.top = y + 'px'
	}

	var addEventListener = function(element, event, listener, capture)
	{
		if(element.addEventListener)
			element.addEventListener(event, listener, capture)
		else
		{
			if(element.attachEvent)
				element.attachEvent('on' + event, listener)
		}
	}

	var replaceStrings = function(value, replace)
	{
		for(var v in replace)
		{
			var c = ''
			for(var p = 0; p < value.length;)
			{
				var f = value.indexOf(v, p)
				if(f == -1)
				{
					c += value.substring(p)
					break
				}
				c += value.substring(p, f) + replace[v]
				p = f + v.length
			}
			value = c
		}
		return value
	}

	var encodeHTML = function(value)
	{
		return replaceStrings(value, {
			'&': '&amp;',
			'"': '&quot;',
			'<': '&lt;',
			'>': '&gt;'
		})
	}

	var escapeHTML = function(value)
	{
		return value.replace(/<a([^>]*)>([^<]*)<\/a>/gi, '<span style="' + ML.HTMLEditor.linkEmulationStyle + '">$2</span>')
	}

	var encodeString = function(value)
	{
		return "'" + replaceStrings(value, {
			"'": "\\'"
		}) + "'"
	}

	var expandTemplates = function(e, value, insert)
	{
		var variables = {}, created = {}
		if(insert)
		{
			for(var v in e.templateVariables)
			{
				v = e.openVariable + v + e.closeVariable
				var r = getElementsByName(e.editorDocument, v, ['div', 'span'])
				for(var i = 0; i < r.length; ++i)
				{
					var n = (variables[v] ? variables[v].length : 0)
					if(n == 0)
						variables[v] = []
					variables[v][n] = r[i]['id']
				}
			}
		}
		var style = e.templateVariableStyle.length ? ' style="' + encodeHTML(e.templateVariableStyle) + '"' : ''
		var v = value
		value = ''
		for(var p = 0; p < v.length;)
		{
			var b = v.indexOf(e.openVariable, p)
			if(b == -1)
			{
				value += v.substring(p)
				break
			}
			b += e.openVariable.length;
			var f = v.indexOf(e.closeVariable, b)
			if(f == -1)
			{
				value += v.substring(p)
				break
			}
			var s = v.indexOf(' ', b)
			if(s == -1
			|| s > f)
				s = f
			var tv = v.substring(b, s)
			if(e.templateVariables[tv])
			{
				var a

				if(e.templateVariables[tv].alternatives)
				{
					a = (s == f ? null : v.substring(s + 1, f))
					if(!e.templateVariables[tv].alternatives[a])
						a = null
				}
				else
					a = null
				var t = (e.templateVariables[tv].inline ? 'span' : 'div')
				var i = encodeHTML(e.openVariable + tv + e.closeVariable)
				var n = (variables[i] ? variables[i].length : 0)
				value += v.substring(p, b - e.openVariable.length) + '<' + t + ' id="' + i + '_' + n +'" name="' + i + '"' + style + ' contentEditable="false"' + (e.templateVariables[tv].title ? ' title="' + encodeHTML(e.templateVariables[tv].title) + '"' : '') + (a ? ' data="' + encodeHTML(a) + '"' : '') + '></' + t + '>'
				p = f + e.closeVariable.length
				if(n == 0)
					variables[i] = []
				var c = (created[tv] ? created[tv].length : 0)
				if(c == 0)
					created[tv] = []
				variables[i][n] = created[tv][c] = i + '_' + n
			}
			else
			{
				value += v.substring(p, b)
				p = b
			}
		}
		return { value: value, created: created }
	}

	var handleMenus = function(e)
	{
		return function(event)
		{
			var id, parent

			if(event.target)
				id = (parent = event.target).id
			else
			{
				if(event.srcElement)
					id = (parent = event.srcElement).id
				else
					return
			}
			if(id.length == 0
			|| !parent)
				return
			var now = (new Date()).getTime()
			if(e.lastMenu != id
			|| now - e.lastMenuTime > e.menuDelayTime)
			{
				var menu = e.visualEditor.ownerDocument.getElementById(id + '_menu');
				if(e.toggleMenu(menu, parent, e.iframe))
					menu.setAttribute('data', parent.parentNode.id)
				e.lastMenu = id
				e.lastMenuTime = now
			}
			if(event)
			{
				if(event.preventDefault)
					event.preventDefault()
				event.cancelBubble = true
				event.returnValue = false
			}
		}
	}

	this.formatMark = function(variable, alternative)
	{
		var e = this

		return '<span style="float: left; ' + encodeHTML(e.templateMarkStyle) + '">' + encodeHTML(e.openVariable + variable + (alternative ? ' ' + alternative : '') + e.closeVariable + (e.templateVariables[variable].alternatives ? ' ' + e.alternativesMark : '')) + '</span><br style="clear: both">' + escapeHTML(alternative ? e.templateVariables[variable].alternatives[alternative].preview : e.templateVariables[variable].preview)
	}

	this.changeMark = function(mark, variable, alternative)
	{
		var e = this
		var i = e.editorDocument.createElement('button')
		i.setAttribute('id', e.id + '_' + variable)
		var s = (e.templateVariables[variable].inline ? '' : 'display: block; width: 100%; text-align: left; ') + 'background-color: inherit; border-style: none; border-width: 0px; padding: 0px; font-family: inherit; font-size: inherit; color: inherit'
		if(i.currentStyle)
			i.style.cssText = s
		else
			i.setAttribute('style', s)
		i.setAttribute('contentEditable', 'false')
		i.innerHTML = e.formatMark(variable, alternative)
		mark.innerHTML = ''
		mark.appendChild(i)
	}

	this.setAlternative = function(mark, variable, alternative)
	{
		var e = this, p, i

		p = e.editorDocument.getElementById(mark)
		if(alternative)
			p.setAttribute('data', alternative)
		else
		{
			p.setAttribute('data', '')
			p.removeAttribute('data')
		}
		e.changeMark(p, variable, alternative)
	}

	this.loadTemplates = function()
	{
		var e = this, setup = 'var e = ML.HTMLEditor.HTMLEditors[\'' + e.id + '\']; '

		for(var v in e.templateVariables)
		{
			var r = getElementsByName(e.editorDocument, e.openVariable + v + e.closeVariable, [ 'div', 'span' ])
			for(var n = 0; n < r.length; ++n)
			{
				var i, s, id = e.id + '_' + v

				r[n].innerHTML = ''
				if(e.templateVariables[v].alternatives)
				{
					i = e.visualEditor.ownerDocument.createElement('div')
					i.setAttribute('id', id + '_menu')
					e.visualEditor.appendChild(i)
					s = e.menuStyle + '; position: absolute; visibility: hidden'
					if(i.currentStyle)
						i.style.cssText = s
					else
						i.setAttribute('style', s)
					var o = {}
					o[v] = e.templateVariables[v].title
					for(var a in e.templateVariables[v].alternatives)
						o[a] = e.templateVariables[v].alternatives[a].title
					var oo = ''
					for(var a in o)
						oo += '<div style="' +  encodeHTML(e.itemStyle) + '" onmouseover="' + encodeHTML('var s = ' + encodeString(e.itemSelectStyle) + '; if(this.currentStyle) { this.style.cssText = s } else { this.setAttribute(\'style\', s) }') + '" onmouseout="' + encodeHTML('var s = ' + encodeString(e.itemStyle) + '; if(this.currentStyle) { this.style.cssText = s } else { this.setAttribute(\'style\', s) }') + '" onmousedown="' + setup + 'e.setAlternative(this.ownerDocument.getElementById(' + encodeString(id + '_menu') + ').getAttribute(\'data\'), ' + encodeHTML(encodeString(v) + ', ' + (oo.length ? encodeString(a) : 'null')) +'); e.hideMenu(this.ownerDocument.getElementById(' + encodeString(id + '_menu') + ')); return false">' + o[a] + '</div>'
					i.innerHTML = oo
				}
				e.changeMark(r[n], v, (e.templateVariables[v].alternatives && hasAttribute(r[n],'data')) ? r[n].getAttribute('data') : null)
				if(e.templateVariables[v].alternatives)
					addEventListener(r[n], 'click', handleMenus(e), true)
			}
		}
	}

	this.hideMenu = function(menu)
	{
		if(menu)
		{
			menu.style.visibility = 'hidden'
			this.lastOpenedMenu = null
		}
	}

	this.showMenu = function(menu, parent, frame)
	{
		if(menu)
		{
			if(this.lastOpenedMenu
			&& this.lastOpenedMenu.id != menu.id)
				this.hideMenu(this.lastOpenedMenu)
			repositionElement(menu, parent, frame)
			menu.style.visibility = 'visible'
			this.lastOpenedMenu = menu
			return true
		}
		return false
	}

	this.toggleMenu = function(menu, parent, frame)
	{
		if(menu)
		{
			if(menu.style.visibility == 'visible')
				this.hideMenu(menu)
			else
				return this.showMenu(menu, parent, frame)
		}
		return false
	}

	var convertValue = function(e, toEditor)
	{
		var value

		if(toEditor)
		{
			value = expandTemplates(e, e.textarea.value, false).value
			e.editorDocument.body.innerHTML = value
			return value
		}
		else
		{
			var v, n, r, t = document.getElementById(e.id + '_temporary')
			t.innerHTML = e.editorDocument.body.innerHTML
			for(v in e.templateVariables)
			{
				r = getElementsByName(t, e.openVariable + v + e.closeVariable, ['div', 'span'])
				for(n = 0; n < r.length; ++n)
					r[n].parentNode.replaceChild(document.createTextNode(e.openVariable + v + (hasAttribute(r[n], 'data') ? ' ' + r[n].getAttribute('data') : '') + e.closeVariable), r[n])
			}
			value = t.innerHTML
			t.innerHTML = ''
			return value
		}
	}

	this.synchronize = function()
	{
		var e = this
		var converted = convertValue(e, false)
		if(converted != e.textarea.value)
		{
			if(e.mode == 'visual')
			{
				e.textarea.value = converted
				if(typeof(e.textarea.onchange) == 'function')
					e.textarea.onchange()
			}
			else
			{
				convertValue(e, true)
			}
		}
	}

	var renderToolbar = function(e, toolbars)
	{
		var render = '<table>'
		var setup = 'var e = ML.HTMLEditor.HTMLEditors[\'' + e.id + '\']; '
		for(var t in toolbars)
		{
			render += '<tr><td>'
			for(var b = 0; b < toolbars[t].length; ++b)
			{
				var type = toolbars[t][b].type
				switch(type)
				{
					case 'visual':
					case 'html':
					case 'bold':
					case 'italic':
					case 'underline':
					case 'strikethrough':
					case 'createlink':
					case 'unlink':
					case 'copy':
					case 'cut':
					case 'paste':
					case 'delete':
					case 'undo':
					case 'redo':
					case 'html':
					case 'visual':
					case 'justifyleft':
					case 'justifycenter':
					case 'justifyright':
					case 'justifyfull':
						action = setup + 'if(!e.execCommand("' +  type + '", true) && !e.debug) { alert("Could not execute the ' + type + ' command in this browser.") }'
						switch(type)
						{
							case 'bold':
								content = '<span style="font-weight: bold">B</span>'
								title = 'Bold'
								break
							case 'italic':
								content = '<span style="font-style: italic">I</span>'
								title = 'Italic'
								break
							case 'underline':
								content = '<span style="text-decoration: underline">U</span>'
								title = 'Underline'
								break
							case 'strikethrough':
								content = '<span style="text-decoration: line-through">S</span>'
								title = 'Strike-through'
								break
							case 'createlink':
								content = '<span style="text-decoration: underline; color: #0000ff">www</span>'
								title = 'Create link'
								action = 'var url = prompt("Link URL:", "http://www."); if(url == null || url == "") return false; ' + setup + 'if(!e.execCommand("' +  type + '", url) && !e.debug) { alert("Could not execute the ' +  type + ' command in this browser.") }';
								break
							case 'unlink':
								content = '<span style="text-decoration: underline line-through; color: #0000ff">www</span>'
								title = 'Remove link'
								break
							case 'copy':
								content = 'Copy'
								title = 'Copy selected'
								break
							case 'cut':
								content = 'Cut'
								title = 'Cut selected'
								break
							case 'paste':
								content = 'Paste'
								title = 'Paste copied'
								break
							case 'delete':
								content = 'Delete'
								title = 'Delete selected'
								break
							case 'undo':
								content = 'Undo'
								title = 'Undo'
								break
							case 'redo':
								content = 'Redo'
								title = 'Redo'
								break
							case 'justifyleft':
								content = 'Left'
								title = 'Justify left'
								break
							case 'justifycenter':
								content = 'Center'
								title = 'Justify center'
								break
							case 'justifyright':
								content = 'Right'
								title = 'Justify right'
								break
							case 'justifyfull':
								content = 'Full'
								title = 'Justify full'
								break
							case 'visual':
								content = 'Visual'
								title = 'Edit visually'
								action = setup + 'e.setEditMode("visual");'
								break
							case 'html':
								content = 'HTML'
								title = 'Edit HTML'
								action = setup + 'e.setEditMode("html");'
								break
							default:
								if(e.debug)
									alert('toolbar element of type ' +  type + ' is not implemented')
								action = ''
								content = title = type
						}
						if(action.length)
							render += '<button onclick="' + encodeHTML(action) + ' return false" title="' + encodeHTML(title) + '">' + content + '</button>'
						break

					case 'justify':
					case 'inserttemplate':
					case 'formatblock':
						var action, o, oo, menu

						switch(type)
						{
							case 'justify':
								menu = 'Justify'
								o = {
									'justifyleft': 'Left',
									'justifycenter': 'Center',
									'justifyright': 'Right',
									'justifyfull': 'Full'
								}
								action = 'if(!e.execCommand({item}, true) && !e.debug) { alert("Could not execute the justify command in this browser.") }'
								break

							case 'inserttemplate':
								menu = 'Insert template'
								o = {}
								if(e.templateVariables.length == 0)
									break
								var comma = ''
								for(v in e.templateVariables)
								{
									o[e.openVariable + v + e.closeVariable] = (e.templateVariables[v].title ? e.templateVariables[v].title : v)
									comma = ', '
								}
								action = 'if(!e.execCommand("inserthtml", {item}) && !e.debug) { alert("Could not execute the inserthtml command in this browser.") }; e.loadTemplates();'
								break

							case 'formatblock':
								menu = 'Format block'
								o = {
									'p': '<p style="margin: 0px">Paragraph</p>',
									'pre': '<pre style="margin: 0px">Preformatted</pre>',
									'address': '<address style="margin: 0px">Address</address>',
									'h1': '<h1 style="margin: 0px">Heading 1</h1>',
									'h2': '<h2 style="margin: 0px">Heading 2</h2>',
									'h3': '<h3 style="margin: 0px">Heading 3</h3>',
									'h4': '<h4 style="margin: 0px">Heading 4</h4>',
									'h5': '<h5 style="margin: 0px">Heading 5</h5>',
									'h6': '<h6 style="margin: 0px">Heading 6</h6>'
								}
								action = 'if(!e.execCommand("formatblock", {item} ) && !e.debug) { alert("Could not execute the formatblock command in this browser.") }'
								break
						}
						var id = e.id + '_' + type
						oo = ''
						for(var v in o)
							oo += '<div style="' +  encodeHTML(e.itemStyle) + '" onmouseover="' + encodeHTML('var s = ' + encodeString(e.itemSelectStyle) + '; if(this.currentStyle) { this.style.cssText = s } else { this.setAttribute(\'style\', s) }') + '" onmouseout="' + encodeHTML('var s = ' + encodeString(e.itemStyle) + '; if(this.currentStyle) { this.style.cssText = s } else { this.setAttribute(\'style\', s) }') + '" onmousedown="' + setup + encodeHTML(replaceStrings(action, { '{item}': encodeString(v) })) + '; e.hideMenu(this.ownerDocument.getElementById(' + encodeString(id) + ')); return false">' + o[v] + '</div>'
						render += '<div id="' + id + '" style="' + encodeHTML(e.menuStyle) + '; position: absolute; visibility: hidden; white-space: nowrap">' + oo + '</div><button onkeydown="if(event.keyCode == 27) { ' + setup + 'e.hideMenu(this.ownerDocument.getElementById(' + encodeString(id) + ')); return false }" onclick="' + setup + 'e.toggleMenu(this.ownerDocument.getElementById(' + encodeString(id) + '), this, null); return false" title="' + encodeHTML(menu) + '">' + menu + '</button>'
						break

					case 'separator':
						render += (toolbars[t][b].content ? toolbars[t][b].content : ' | ')
						break

					case 'space':
						render += (toolbars[t][b].content ? toolbars[t][b].content : '&nbsp;')
						break

					default:
						if(e.debug)
							alert('toolbar element of type ' +  type + ' is not implemented')
						break
				}
			}
			render += '</td></tr>'
		}
		render += '</table>'
		return render
	}

	this.setError = function(error)
	{
		this.error = error
		if(this.debug)
			alert(error)
		return false
	}

	this.insertEditor = function(editor, textarea)
	{
		var e = this
		var l = document.getElementById(editor)
		if(!l)
			return e.setError('editor block "' + editor + ' was not found in this document')
		e.id = textarea.id
		var editor_document =  editor + '_iframe'
		var html_editor = editor + '_html'
		var visual_editor = editor + '_visual'
		l.innerHTML = '<div id="' + encodeHTML(html_editor) + '">' + renderToolbar(e, ML.HTMLEditor.defaultHTMLToolbar) + '<div><textarea id="' + encodeHTML(textarea.id) + '" name="' + encodeHTML(textarea.name) + (textarea.rows ? '" rows="' + textarea.rows : '') + (textarea.cols ? '" cols="' + textarea.cols : '') + (textarea.className ? '" class="' + encodeHTML(textarea.className) : '') + (textarea.style ? '" style="' + encodeHTML(textarea.style) : '') + '">' + (textarea.value ? encodeHTML(textarea.value) : '') + '</textarea></div></div><div id="' + encodeHTML(visual_editor) + '">' + renderToolbar(e, ML.HTMLEditor.defaultVisualToolbar) + '<div><iframe id="' + encodeHTML(editor_document) + '" src="javascript:;" frameborder="0" marginwidth="0" marginheight="0" style="' + encodeHTML(textarea.style ? textarea.style : e.editorStyle) + '"></iframe></div></div><div id="' + e.id + '_temporary" style="display: none"></div>'
		if(!(e.htmlEditor = document.getElementById(html_editor)))
			return e.setError('could not create the HTML editor section')
		if(!(e.visualEditor = document.getElementById(visual_editor)))
			return e.setError('could not create the visual editor section')
		if(!(e.textarea = document.getElementById(textarea.id)))
			return e.setError('could not create the editor textarea')
		if(!(l = e.iframe = document.getElementById(editor_document)))
			return e.setError('could not create the editor iframe')
		if(!l.contentWindow
		|| !l.contentWindow.document)
			return e.setError('could not access the editor iframe')
		e.editorDocument = l.contentWindow.document
		e.editorDocument.open()
		e.editorDocument.write('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><title>Editor</title>')
		if(e.externalCSS.length)
		{
			for(var c = 0; c < e.externalCSS.length; ++c)
				e.editorDocument.write('<link rel="stylesheet" type="text/css" href="' + encodeHTML(e.externalCSS[c]) + '">')
		}
		e.editorDocument.write('</head><body></body></html>')
		e.editorDocument.close()
		var html_size = getElementSize(e.htmlEditor)
		var visual_size = getElementSize(e.visualEditor)
		var size = getElementSize(e.textarea)
		var i = getElementSize(e.iframe)
		l.style.width = size.width
		l.style.height = i.height + html_size.height - visual_size.height
		e.htmlEditor.style.display = 'none'
		convertValue(e, true)
		if(typeof(e.editorDocument.designMode) == 'string'
		&& e.editorDocument.designMode == 'off')
			e.editorDocument.designMode = 'on'
		else
		{
			if(typeof(e.editorDocument.body.contentEditable) != 'undefined')
				e.editorDocument.body.contentEditable = true
			else
				return e.setError('HTML editing in this browser is not yet supported')
		}
		addEventListener(e.textarea, 'change', function()
			{
				e.synchronize()
			}, false
		)
		addEventListener(e.editorDocument.body, 'blur', function()
			{
				this.synchronize()
			}, false
		)
		ML.HTMLEditor.HTMLEditors[textarea.id] = e
		e.loadTemplates()
		return true
	}

	this.execCommand = function(command, argument)
	{
		var exception, e = this

		if(e.editorDocument == null)
			return e.setError('the HTML editor elements are not yet setup')
		try
		{
			var enabled, expanded

			switch(command)
			{
				case 'inserthtml':
					try
					{
						enabled = e.editorDocument.queryCommandEnabled(command)
					}
					catch(exception)
					{
						enabled = false
					}
					expanded = expandTemplates(e, argument, true)
					argument = expanded.value
					break
				default:
					enabled = true
			}
			if(enabled)
				e.editorDocument.execCommand(command, false, argument)
			switch(command)
			{
				case 'copy':
				case 'cut':
				case 'paste':
					if(!e.editorDocument.queryCommandSupported(command))
						throw('Not supported')
					break
				case 'inserthtml':
					if(!enabled)
					{
						e.editorDocument.body.focus()
						var r = e.editorDocument.selection.createRange()
						r.pasteHTML(argument)
					}
/*
					for(var v in expanded.created)
					{
						if(e.templateVariables[v].alternatives)
						{
							for(var i = 0; i < expanded.created[v].length; ++i)
								addEventListener(e.editorDocument.getElementById(expanded.created[v][i]), 'click', handleMenus(e), true)
						}
					}
*/
			}
		}
		catch(exception)
		{
			return e.setError(command + ' command is not allowed in this browser: ' + exception.message)
		}
		return true
	}

	this.setEditMode = function(mode)
	{
		switch(mode)
		{
			case 'visual':
				this.textarea.blur()
				this.htmlEditor.style.display = 'none'
				this.synchronize()
				this.visualEditor.style.display = 'block'
				this.loadTemplates()
				this.editorDocument.body.focus()
				break

			case 'html':
				this.editorDocument.body.blur()
				this.visualEditor.style.display = 'none'
				this.synchronize()
				this.htmlEditor.style.display = 'block'
				this.textarea.focus()
				break

			default:
				return this.setError(mode + ' is not valid edit mode')
		}
		this.mode = mode
		return true
	}
}

}
